/* eslint-disable no-unused-vars */
function ZPack(opt) {
    this.opt = opt;
    var zpack = this;
    var modules = this.modules = {};

    this.entry = opt.entry;
    var scriptContainer = document.createElement('div');
    scriptContainer.id = 'zpackScriptContainer';
    document.body.appendChild(scriptContainer);

    function fetch(key) {
        
        return new Promise(function(resolve, reject) {
            var module = modules[key];
            if (module) {
                return resolve(module);
            }
            module = zpack.declare(key);
            module.onFetched = function() {
                var fetches = [];
                if (module.children) {
                    module.children.forEach(function(e) {
                        fetches.push(new Promise(function(resolve, reject) {
                            fetch(e).then(function(submodule) {
                                if (submodule.parent == null) {
                                    submodule.parent = module;
                                }
                                resolve(submodule);
                            }).catch(reject);
                        }));
                    });
                }
                Promise.all(fetches)
                    .then(() => {
                        resolve(module);
                        delete module.onFetched;
                    }).catch(reject);
            };
            let script = document.createElement('script');
            script.className = 'zpackScript';
            script.dataset.key = key;
            script.src = key;
            script.onerror = function(e) {
                reject(e);
            };
            scriptContainer.appendChild(script);
        });
    }

    function require(key) {
        var module = modules[key];
        if (module) {
            module.load();
            return module.exports;
        } else {
            throw new Error("module '" + key + "' not found");
        }
    }

    require.dynamic = function(key) {
        return new Promise(function(resolve, reject) {
            var module = modules[key];
            if (module && (module.key === 'ready' || module.key === 'loaded')) {
                resolve(module);
            } else {
                fetch(key)
                    .then(function(module) {
                        resolve(require(module.id));
                    }).catch(reject);
            }
        });
    };

    this.define = function(opt, def) {
        var dynamicKey = getDynamicKey();
        if (dynamicKey == null) {
            dynamicKey = opt.id;
        }
        var module = zpack.declare(dynamicKey);
        if (module.status !== 'new') {
            // eslint-disable-next-line no-console
            console.warn("module '" + module.id + "' duplicated");
            return;
        }
        module.dependencyMap = opt.dependency || {};
        for (var i in zpack.opt.external) {
            if (module.dependencyMap[i]) {
                delete module.dependencyMap[i];
            }
        }
        modules[opt.id] = module;
        module.id = opt.id;
        module.children = Object.values(module.dependencyMap);
        module.def = def;
        module.status = 'ready';
        //console.log("module '"+module.id+"' is ready");
        if (module.onFetched) {
            module.onFetched();
        }
    };

    function getDynamicKey() {
        let script = document.currentScript;
        if (script && script.className === 'zpackScript') {
            return script.dataset.key;
        }
    }

    this.declare = function(key) {
        //console.log("declare:",key);
        var module = modules[key];
        if (module) {
            return module;
        }
        module = {
            exports: {},
            id: key,
            path: key,
            paths: [key],
            children: [],
            type: 'normal',
            status: 'new'
        };
        Object.defineProperty(module, 'loaded', {
            get: function() {
                return this.status === 'loaded';
            }
        });
        module.load = function() {
            if (module.loaded) {
                return;
            }
            if (module.status === 'loading') {
                return;
            }
            if (module.status !== 'ready') {
                throw new Error("module '" + key + "' status '" + module.status + "' is invalid");
            }
            module.status = 'loading';
            module.def(function(key) {
                if (opt.external[key] == null) {
                    if (module.dependencyMap && module.dependencyMap[key]) {
                        key = module.dependencyMap[key];
                    }
                }
                return require(key);
            }, module.exports, module, '/', key);
            module.status = 'loaded';
            module.loaded = true;
        };
        return modules[key] = module;
    };

    if (opt.external) {
        if (opt.external) {
            for (var i in opt.external) {
                (function(i) {
                    zpack.define({
                        'id': i,
                        'dependency': {}
                    }, (require, exports, module) => module.exports = opt.external[i]);
                })(i);
            }
        }
    }

    if (opt.library) {
        opt.library.forEach(function(lib) {
            let script = document.createElement('script');
            script.src = lib;
            document.body.appendChild(script);
        });
    }

    if (!window.global) {
        window.global = window;
    }
    if (!window.process) {
        window.process = {
            pid: 0,
            env: {},
            version: '',
            platform: navigator.userAgent,
            execPath: location.href
        };
    }
    if (!window.require) {
        window.require = require;
    }
}
